/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package knn;

import java.lang.reflect.Array;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *
 * @author Nguyen Thuy Ngoc <ntngoc1988@gmail.com>
 */
public class knnProcessor<T extends ISample> {

    private int k = 1;
    List<T> trainingSet;
    IMetric<T> metric;
    IDominantSelector<T> dominant;

    // Constructor of processor of kNN with trainingSet, specific metric, and constant k
    public knnProcessor(List<T> trainingSet, IMetric<T> metric, int k, IDominantSelector dominant) {
        this.trainingSet = trainingSet;
        this.metric = metric;
        this.k = k;
        this.dominant = dominant;
    }

    // Predict the type of sample
    public String predict(T sample) {
        boolean flag = true;
        T[] nearestSamples = (T[]) Array.newInstance(sample.getClass(), getK());
        double[] nearestDistances = new double[getK()];
        for (int i = 0; i < getK(); i++) {
            nearestDistances[i] = Double.MAX_VALUE;
        }
        int farestIndex = 0;

        for (T trainingSample : this.trainingSet) {
            double distance = this.metric.calcDistance(trainingSample, sample);

            if (distance < nearestDistances[farestIndex]) {
                nearestSamples[farestIndex] = trainingSample;
                nearestDistances[farestIndex] = distance;

                // update current farest index
                for (int j = 0; j < getK(); j++) {
                    if(j == farestIndex) {
                        continue;
                    }
                    if (nearestDistances[j] > nearestDistances[farestIndex]) {
                        farestIndex = j;
                    }
                }
            }
        }
        for (int i = 0; i < nearestDistances.length; i++) {
            if (nearestDistances[i] == 0.0) {
                flag = false;
            }
        }
        if (dominant instanceof WeightedDominantSelector) {
            if (flag == false) {
                return determineDominantClass(nearestSamples);
            }
        }
        return dominant.determineDominantClass(nearestSamples, nearestDistances);
    }

    private String determineDominantClass(T[] samples) {
        Map<String, Integer> countPerClass = new HashMap<String, Integer>();

        String dominantClass = null;
        Integer dominantCount = 0;

        for (T sample : samples) {
            if (countPerClass.containsKey(sample.getAssignedClass())) {
                Integer count = countPerClass.get(sample.getAssignedClass()) + 1;
                countPerClass.put(sample.getAssignedClass(), count);
            } else {
                countPerClass.put(sample.getAssignedClass(), 1);
            }

            if (countPerClass.get(sample.getAssignedClass()) > dominantCount) {
                dominantClass = sample.getAssignedClass();
                dominantCount = countPerClass.get(sample.getAssignedClass());
            }
        }

        return dominantClass;
    }

    public int getK() {
        return k;
    }
}